package niu.toy.spring.support.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.TypeUtil;
import niu.toy.spring.support.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Array;
import java.lang.reflect.Type;
import java.util.*;

/**
 * 此wrapper的实现参考spring的beanWrapper的实现,最主要的不同：spring使用嵌套递归的方法访问或设置属性并带有缓存，本方法使用栈，没有使用缓存
 * @author niu
 */
public class BeanWrapperImpl implements BeanWrapper {
    private static final Logger logger = LoggerFactory.getLogger(BeanWrapperImpl.class);

    public Object wrappedObject;
    private boolean autoGrowNestedPaths = true;

    public BeanWrapperImpl(Object wrappedObject) {
        this.wrappedObject = wrappedObject;
    }

    public Object getWrappedInstance() {
        return wrappedObject;
    }

    public Class<?> getPropertyType(String propertyName) throws BeansException {
        return ReflectUtil.getField(this.wrappedObject.getClass(), propertyName).getType();
    }

    public Class<?> getWrappedClass() {
        return wrappedObject.getClass();
    }

    public boolean isAutoGrowNestedPaths() {
        return autoGrowNestedPaths;
    }

    public void setAutoGrowNestedPaths(boolean autoGrowNestedPaths) {
        this.autoGrowNestedPaths = autoGrowNestedPaths;
    }

    /**
     *propertyName路径是有层次的，一般访问或设置当前层属性后，才能继续操作
     */
    public Object getPropertyValue(String propertyName) throws BeansException {
        Stack<Object> parents = new Stack<Object>();
        parents.push(wrappedObject);

        Stack<String> childPaths = new Stack<String>();
        List<String> pathList = Arrays.asList(propertyName.split("\\."));
        Collections.reverse(pathList);
        pathList.forEach(path -> childPaths.push(path));

        Object fieldValue = null;
        while (!childPaths.empty()) {
            String childPath = childPaths.pop();
            Object parent = parents.pop();
            PropertyTokenHolder tokens = PropertyAccessorUtils.getPropertyNameTokens(childPath);
            Object value = getPropertyValue(tokens, parent);
            if (childPaths.empty()) {
                return value;
            } else {
                parents.push(value);
            }
        }
        return fieldValue;
    }

    /**
     * 访问并创建
     * @param tokens
     * @param bean
     * @return 返回:若不存在根据actualName创建对象,若存在就是get
     * @throws BeansException
     */
    protected Object getPropertyValue(PropertyTokenHolder tokens, Object bean) throws BeansException {
        if (bean == null) {
            return null;
        }
        String propertyName = tokens.canonicalName;
        String actualName = tokens.actualName;
        PropertyHandler ph = new PropertyHandler(bean, tokens.actualName);
        try {
            Object value = ph.getValue();
            if (tokens.keys != null) {
                if (value == null) {
                    if (isAutoGrowNestedPaths()) {// 属性不存在，就创建
                        value = setDefaultValue(new PropertyTokenHolder(tokens.actualName),bean);
                    }
                    else {
                        throw new BeansException(propertyName +
                                "Cannot access indexed value of property referenced in indexed " +
                                "property path '" + propertyName + "': returned null");
                    }
                }
                StringBuilder indexedPropertyName = new StringBuilder(tokens.actualName);
                // apply indexes and map keys   根据keys创建对象或容器
                for (int i = 0; i < tokens.keys.length; i++) {
                    String key = tokens.keys[i];
                    if (value == null) {
                        throw new BeansException(propertyName +
                                "Cannot access indexed value of property referenced in indexed " +
                                "property path '" + propertyName + "': returned null");
                    } else if (value.getClass().isArray()) {
                        int index = Integer.parseInt(key);
                        value = PropertyAccessorUtils.growArrayIfNecessary(value, index, indexedPropertyName.toString());
                        this.setPropertyValue(indexedPropertyName.toString(),value);//可能扩容，导致数组引用改变
                        value = Array.get(value, index);
                    } else if (value instanceof List) {
                        int index = Integer.parseInt(key);
                        List<Object> list = (List<Object>) value;
                        Class<?> elementType = TypeUtil.getClass(ph.getCollectionType(i + 1));
                        PropertyAccessorUtils.growCollectionIfNecessary(list, index, indexedPropertyName.toString(), elementType, i + 1);
                        value = list.get(index);
                    } else if (value instanceof Set) {
                        // Apply index to Iterator in case of a Set.
                        Set<Object> set = (Set<Object>) value;
                        int index = Integer.parseInt(key);
                        if (index < 0 || index >= set.size()) {
                            throw new BeansException(propertyName +
                                    "Cannot get element with index " + index + " from Set of size " +
                                    set.size() + ", accessed using property path '" + propertyName + "'");
                        }
                        Iterator<Object> it = set.iterator();
                        for (int j = 0; it.hasNext(); j++) {
                            Object elem = it.next();
                            if (j == index) {
                                value = elem;
                                break;
                            }
                        }
                    } else if (value instanceof Map) {
                        Map<Object, Object> map = (Map<Object, Object>) value;
                        Class<?> mapKeyType = TypeUtil.getClass(ph.getMapKeyType(i + 1));
                        // IMPORTANT: Do not pass full property name in here - property editors
                        // must not kick in for map keys but rather only for map values.
                        Object convertedMapKey = Convert.convert(mapKeyType, key);
                        value = map.get(convertedMapKey);
                    } else {
                        throw new BeansException(propertyName +
                                "Property referenced in indexed property path '" + propertyName +
                                "' is neither an array nor a List nor a Set nor a Map; returned value was [" + value + "]");
                    }
                    indexedPropertyName.append(PROPERTY_KEY_PREFIX).append(key).append(PROPERTY_KEY_SUFFIX);
                }
            }
            return value;
        } catch (Exception ex) {
            throw new BeansException(propertyName +
                    "Illegal attempt to get property '" + actualName + "' threw exception", ex);
        }
    }

    /**
     *propertyName路径是有层次的，一般访问或设置当前层属性后，才能继续操作
     */
    @Override
    public void setPropertyValue(String propertyName, Object value) throws BeansException {
        Stack<Object> beans = new Stack<Object>();
        beans.push(wrappedObject);
        Stack<String> childPaths = new Stack<String>();

        List<String> pathList = Arrays.asList(propertyName.split("\\."));
        Collections.reverse(pathList);
        pathList.forEach(path -> childPaths.push(path));

        while (!childPaths.empty()) {
            String childPath = childPaths.pop();
            Object bean = beans.pop();
            PropertyTokenHolder tokens = PropertyAccessorUtils.getPropertyNameTokens(childPath);

            System.out.println("setPropertyValue------bean:"+bean+",childPath="+childPath);
            if (childPaths.empty()) {
                setPropertyValue(tokens, bean, value);
            } else {
                Object propertyValue = this.getPropertyValue(tokens, bean);//创建属性若不存在
                System.out.println("getPropertyValue------bean:"+bean+",childPath="+childPath+",propertyValue="+propertyValue);
                if (propertyValue == null) {//根据key创建value对象
                    propertyValue = setDefaultValue(tokens, bean);
                }
                beans.push(propertyValue);
            }
        }
    }

    protected Object setDefaultValue(PropertyTokenHolder tokens, Object bean) throws BeansException {
        //create
        PropertyHandler ph = new PropertyHandler(bean, tokens.actualName);
        TypeHelper helper=new TypeHelper(ph.getType());
        int nestedLevel=1;
        if(tokens.keys!=null){
            nestedLevel=tokens.keys.length  +1;
        }else{
            nestedLevel=1;
        }
        Type type=helper.nested(nestedLevel);
        Type subType=helper.getSubType(type);

        Object propertyValue = PropertyAccessorUtils.newValue(TypeUtil.getClass(type), TypeUtil.getClass(subType), tokens.actualName);
        setPropertyValue(tokens, bean, propertyValue);
        //直接返回propertyValue 导致相同对象 值不同
//        propertyValue= this.getPropertyValue(tokens, bean);
        return  propertyValue;
    }

    protected void setPropertyValue(PropertyTokenHolder tokens, Object bean, Object propertyValue) throws BeansException {
        if (tokens.keys != null) {
            processKeyedProperty(tokens, bean, propertyValue);
        } else {
            processLocalProperty(tokens, bean, propertyValue);
        }
    }

    public void processLocalProperty(PropertyTokenHolder tokens, Object bean, Object propertyValue) {
        new PropertyHandler(bean, tokens.actualName).setValue(propertyValue);
    }

    private Object getPropertyHoldingValue(PropertyTokenHolder tokens, Object bean) {
//        PropertyTokenHolder getterTokens =PropertyAccessorUtils.getPropertyNameTokens(tokens.parentPropertyPath());// new PropertyTokenHolder(tokens.actualName);
        PropertyTokenHolder getterTokens = new PropertyTokenHolder(tokens.actualName);
        getterTokens.canonicalName = tokens.canonicalName;
        getterTokens.keys = new String[tokens.keys.length - 1];
        System.arraycopy(tokens.keys, 0, getterTokens.keys, 0, tokens.keys.length - 1);
        Object propValue;
        try {
            propValue = getPropertyValue(getterTokens, bean);
        } catch (Exception ex) {
            throw new BeansException(getterTokens.canonicalName + "getter", ex);
        }
        if (propValue == null) {
            int lastKeyIndex = tokens.canonicalName.lastIndexOf('[');
            getterTokens.canonicalName = tokens.canonicalName.substring(0, lastKeyIndex);
            propValue = this.setDefaultValue(getterTokens, bean);
        }
        return propValue;
    }

    private void processKeyedProperty(PropertyTokenHolder tokens, Object bean, Object value) {
        PropertyHandler ph = new PropertyHandler(bean, tokens.actualName);
        Object propValue = getPropertyHoldingValue(tokens, bean);
        String lastKey = tokens.keys[tokens.keys.length - 1];

        if (propValue.getClass().isArray()) {
            Class<?> requiredType = propValue.getClass().getComponentType();
            int arrayIndex = Integer.parseInt(lastKey);
            Object oldValue = null;
            try {
                if (arrayIndex < Array.getLength(propValue)) {
                    oldValue = Array.get(propValue, arrayIndex);
                }
                Object convertedValue = Convert.convert(requiredType, value);
                int length = Array.getLength(propValue);
                if (arrayIndex >= length) {
                    Object newArray = Array.newInstance(requiredType, arrayIndex + 1);
                    System.arraycopy(propValue, 0, newArray, 0, length);
                    //TODO  set newArray to property
                    this.setPropertyValue(tokens.actualName, newArray);
                    propValue=newArray;
                }
                Array.set(propValue, arrayIndex, convertedValue);
            } catch (IndexOutOfBoundsException ex) {
                throw new BeansException(tokens.canonicalName +
                        "Invalid array index in property path '" + tokens.canonicalName + "'", ex);
            }
        } else if (propValue instanceof List) {
            Class<?> requiredType = null;
            List<Object> list = (List<Object>) propValue;
            int index = Integer.parseInt(lastKey);
            Object oldValue = null;
            if (index < list.size()) {
                oldValue = list.get(index);
            }
            Object convertedValue = value;
            int size = list.size();
            if (index >= size) {
                for (int i = size; i < index; i++) {
                    try {
                        list.add(null);
                    } catch (NullPointerException ex) {
                        throw new BeansException(tokens.canonicalName +
                                "Cannot set element with index " + index + " in List of size " +
                                size + ", accessed using property path '" + tokens.canonicalName +
                                "': List does not support filling up gaps with null elements");
                    }
                }
                list.add(convertedValue);
            } else {
                try {
                    list.set(index, convertedValue);
                } catch (IndexOutOfBoundsException ex) {
                    throw new BeansException(tokens.canonicalName +
                            "Invalid list index in property path '" + tokens.canonicalName + "'", ex);
                }
            }
        } else if (propValue instanceof Map) {
            Class<?> mapKeyType = TypeUtil.getClass(ph.getMapKeyType(tokens.keys.length));
            Class<?> mapValueType=TypeUtil.getClass(ph.getMapValueType(tokens.keys.length));

            Map<Object, Object> map = (Map<Object, Object>) propValue;
            // IMPORTANT: Do not pass full property name in here - property editors
            // must not kick in for map keys but rather only for map values.
            Object convertedMapKey = Convert.convert(mapKeyType, lastKey);

            // Pass full property name and old value in here, since we want full
            // conversion ability for map values.
            Object convertedMapValue = value;
            if(value!=null&&!mapValueType.isAssignableFrom(value.getClass())){
                //减少不必要的转换
                convertedMapValue=Convert.convert(mapValueType, value);
            }

            map.put(convertedMapKey, convertedMapValue);
        } else {
            throw new BeansException(tokens.canonicalName +
                    "Property referenced in indexed property path '" + tokens.canonicalName +
                    "' is neither an array nor a List nor a Map; returned value was [" + propValue + "]");
        }
    }



}
